package cn.hamster3.mc.plugin.core.common.util;

import cn.hamster3.mc.plugin.core.common.config.ConfigSection;
import com.google.gson.JsonObject;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.title.Title;
import org.jetbrains.annotations.NotNull;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.time.Duration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@SuppressWarnings("unused")
public final class CoreUtils {
    private CoreUtils() {
    }

    public static void zipCompressionFolder(@NotNull File folder, @NotNull File zipFile) throws IOException {
        try (ZipOutputStream stream = new ZipOutputStream(Files.newOutputStream(zipFile.toPath()))) {
            putFileToZipStream(stream, "", folder);
        }
    }

    public static void putFileToZipStream(@NotNull ZipOutputStream stream, @NotNull String path, @NotNull File file) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files == null) {
                throw new IOException();
            }
            for (File subFile : files) {
                putFileToZipStream(stream, path + file.getName() + "/", subFile);
            }
            return;
        }
        ZipEntry entry = new ZipEntry(path + file.getName());
        stream.putNextEntry(entry);
        stream.write(Files.readAllBytes(file.toPath()));
        stream.closeEntry();
    }

    /**
     * 将 adventure 中的 title 对象序列化为 json
     *
     * @param title -
     * @return -
     */
    @NotNull
    public static JsonObject serializeTitle(@NotNull Title title) {
        JsonObject object = new JsonObject();
        object.add("title", GsonComponentSerializer.gson().serializeToTree(title.title()));
        object.add("subtitle", GsonComponentSerializer.gson().serializeToTree(title.subtitle()));
        Title.Times times = title.times();
        if (times != null) {
            object.add("times", serializeTitleTimes(times));
        }
        return object;
    }

    /**
     * 将 json 反序列化为 adventure 中的 title
     *
     * @param object -
     * @return -
     */
    @NotNull
    public static Title deserializeTitle(@NotNull JsonObject object) {
        if (object.has("times")) {
            return Title.title(
                    GsonComponentSerializer.gson().deserializeFromTree(object.get("title")),
                    GsonComponentSerializer.gson().deserializeFromTree(object.get("subtitle")),
                    deserializeTitleTimes(object.getAsJsonObject("times"))
            );
        } else {
            return Title.title(
                    GsonComponentSerializer.gson().deserializeFromTree(object.get("title")),
                    GsonComponentSerializer.gson().deserializeFromTree(object.get("subtitle"))
            );
        }
    }

    @NotNull
    public static JsonObject serializeTitleTimes(@NotNull Title.Times times) {
        JsonObject object = new JsonObject();
        object.addProperty("fadeIn", times.fadeIn().toMillis());
        object.addProperty("stay", times.stay().toMillis());
        object.addProperty("fadeOut", times.fadeOut().toMillis());
        return object;
    }

    @NotNull
    public static Title.Times deserializeTitleTimes(@NotNull JsonObject object) {
        return Title.Times.times(
                Duration.ofMillis(object.get("fadeIn").getAsLong()),
                Duration.ofMillis(object.get("stay").getAsLong()),
                Duration.ofMillis(object.get("fadeOut").getAsLong())
        );
    }

    @NotNull
    public static JsonObject serializeSound(@NotNull Sound sound) {
        JsonObject object = new JsonObject();
        object.addProperty("key", sound.name().asString());
        object.addProperty("source", sound.source().name());
        object.addProperty("volume", sound.volume());
        object.addProperty("pitch", sound.pitch());
        return object;
    }

    @NotNull
    @SuppressWarnings("PatternValidation")
    public static Sound deserializeSound(@NotNull JsonObject object) {
        return Sound.sound(
                Key.key(object.get("key").getAsString()),
                Sound.Source.valueOf(object.get("source").getAsString()),
                object.get("volume").getAsFloat(),
                object.get("pitch").getAsFloat()
        );
    }

    @NotNull
    public static DataSource getDataSource(@NotNull ConfigSection datasourceConfig) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(datasourceConfig.getString("driver"));
        hikariConfig.setJdbcUrl(datasourceConfig.getString("url"));
        hikariConfig.setUsername(datasourceConfig.getString("username"));
        hikariConfig.setPassword(datasourceConfig.getString("password"));
        hikariConfig.setMaximumPoolSize(datasourceConfig.getInt("maximum-pool-size", 3));
        hikariConfig.setMinimumIdle(datasourceConfig.getInt("minimum-idle", 1));
        long keepAliveTime = datasourceConfig.getLong("keep-alive-time", 0);
        if (keepAliveTime > 5000) {
            hikariConfig.setKeepaliveTime(keepAliveTime);
        }
        hikariConfig.setIdleTimeout(datasourceConfig.getLong("idle-timeout", 10 * 60 * 1000));
        hikariConfig.setMaxLifetime(datasourceConfig.getLong("max-lifetime", 30 * 60 * 1000));
        hikariConfig.setValidationTimeout(datasourceConfig.getLong("validation-timeout", 5000));
        hikariConfig.setPoolName(datasourceConfig.getString("name", "HamsterCore-Pool"));
        return new HikariDataSource(hikariConfig);
    }

    /**
     * 按照给定的表达式计算
     * <p>
     * 如果要在异步线程使用，请自行新建一个 Calculator 实例
     *
     * @param expression 要计算的表达式例如:5+12*(3+5)/7
     * @return 计算结果
     */
    public static double calculate(@NotNull String expression) {
        return Calculator.INSTANCE.calculate(expression);
    }

    @SuppressWarnings("unchecked")
    public static <T> T caseObject(Object o) {
        return (T) o;
    }
}

